﻿using System;
using System.Collections.Generic;
using System.IO;

namespace GeneralDashboard.Data.Core
{
    public class BinaryProtocol : ProtocolBase
    {
        public Memory<byte> FrameHead { get; }
        private bool _initialized;
        private BinaryReaderStatus _status;
        private byte[] _frameHead;
        private int _frameLength;

        private Stream _baseStream;
        private Queue<byte> _readBuffer;

        private Dictionary<Type, int> _typeLengthDict = new Dictionary<Type, int>
        {
            {typeof(int), sizeof(int) },
            {typeof(long), sizeof(long) },
            {typeof(short), sizeof(short) },
            {typeof(byte), sizeof(byte) },
            {typeof(uint), sizeof(uint) },
            {typeof(ulong), sizeof(ulong) },
            {typeof(ushort), sizeof(ushort) },
            {typeof(sbyte), sizeof(sbyte) },
            {typeof(float), sizeof(float) },
            {typeof(double), sizeof(double) },
            {typeof(bool), sizeof(bool) }
        };

        public BinaryProtocol(ReadOnlySpan<byte> frameHead, IDictionary<string, Type> frameSequence) : base(frameSequence)
        {
            FrameHead = frameHead.ToArray();
            _initialized = false;
        }

        public bool Initialise(Stream stream)
        {
            if (stream == null || !stream.CanRead)
                return false;
            _status = BinaryReaderStatus.WaitingForHead;
            _baseStream = stream;
            _readBuffer = new Queue<byte>(512);
            _frameHead = FrameHead.ToArray();
            _frameLength = 0;

            foreach (var item in _frameSequence.Values)
            {
                _frameLength += _typeLengthDict[item];
            }

            _initialized = true;
            return _initialized;
        }

        public override void GetFrameData()
        {
            int dataCount = 0, headCount = 0;
            while (_status == BinaryReaderStatus.WaitingForHead)
            {
                int currentByte;
                if ((currentByte = _baseStream.ReadByte()) == -1) continue;
                while (currentByte == _frameHead[headCount++])
                {
                    if (headCount == _frameHead.Length)
                    {
                        _status = BinaryReaderStatus.InFrame;
                        dataCount = 0;
                        break;
                    }
                }
            }
            while (_status == BinaryReaderStatus.InFrame)
            {
                int currentByte;
                if (dataCount == _frameLength)
                {
                    _status = BinaryReaderStatus.FrameFinish;
                    break;
                }
                if ((currentByte = _baseStream.ReadByte()) == -1) continue;
                _readBuffer.Enqueue((byte)currentByte);
                ++dataCount;
            }
            if (_status == BinaryReaderStatus.FrameFinish)
            {
                foreach (var item in _frameSequence)
                {
                    int _dataLength = _typeLengthDict[item.Value];
                    byte[] _dataArray = new byte[_dataLength];
                    for (int i = 0; i < _dataLength; i++)
                    {
                        _dataArray[i] = _readBuffer.Dequeue();
                    }
                    _dataDict[item.Key].SetValue(_dataArray);
                }
                _status = BinaryReaderStatus.WaitingForHead;
            }
        }
    }

    enum BinaryReaderStatus
    {
        WaitingForHead,
        InFrame,
        FrameFinish
    }
}
